﻿using 舵机调试工具.Properties;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Runtime;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Newtonsoft.Json;
using System.Security.Cryptography;
using static System.Windows.Forms.AxHost;

namespace 舵机调试工具
{
    public partial class Form1 : Form
    {
        #region 初始化代码

        //程序构造函数
        public Form1()
        {
            //初始化界面
            InitializeComponent();                               

            //调用串口列表函数
            RefershPorts();

            //读取配置文件并更新界面
            InitForm();

            //读取舵机配置文件并更新界面
            InitSovers();

            //屏蔽此处代码
            //CB_Baud.SelectedIndex = 0;
        }


        //变量用于保存当前选择的串口号，
        //当重新刷新串口列表的时候
        //依然能够显示之前的串口号
        //注意：在修改串口号的时候修改此变量
        private string savePort = "COM1";

        #endregion

        #region 串口相关的自定义函数

        //串口列表刷新函数，在程序初始化或者关闭串口的时候使用
        //如果看不懂代码，可以直接复制粘贴本函数和上边的那个变量
        private void RefershPorts()
        {
            //string[]表示变量portnames是字符串数组类型，每一个成员都是一个字符串
            //SerialPort.GetPortNames()函数用于获取当前系统的所有串口号列表，类型是string[]
            //注意:函数是SerialPort.GetPortNames()，而不是serialPort1.GetPortNames()
            //GetPortNames()是串口类中共有的函数，所以不能用串口变量来调用此函数
            string[] portnames = SerialPort.GetPortNames();

            //CB_PortName是保存串口名的combobox控件
            //CB_PortName.Items代表控件中所有项目的列表名字
            //CB_PortName.Items.Clear() 函数是为了把此控件中列表的内容全部清空
            CB_PortName.Items.Clear();

            //CB_PortName.Items.AddRange()函数是为了在控件中添加一组数据
            //这里就把刚刚读取的串口号列表传入进去
            CB_PortName.Items.AddRange(portnames);

            //获取更新列表前，选择的串口号对应的索引
            //函数IndexOf()用于根据参数的值查找这个值对应的索引位置（从0开始）
            //如果此时没有那个串口，则返回缩印为-1，表示没有数据
            int newSelect = CB_PortName.Items.IndexOf(savePort);

            //设置新的串口名字索引
            //SelectedIndex表示选择显示索引的属性。
            CB_PortName.SelectedIndex = newSelect;

        }

        //串口打开或者关闭的函数
        private void OpenOrClose()
        {
            try
            {
                //如果当前串口处于关闭状态，则开启串口，先把波特率和串口号赋给串口变量
                if (serialPort1.IsOpen == false)
                {
                    //savePort变量保存当前的串口号
                    //serialPort1.PortName赋值当前的串口号
                    settings.portName = savePort = serialPort1.PortName = CB_PortName.Text;

                    //serialPort1.BaudRate赋值波特率，因为是int类型
                    //所以需要用 int.Parse()函数把CB_Baud.Text字符串转换成数字
                    settings.BaudRate = serialPort1.BaudRate = int.Parse(CB_Baud.Text);

                    //修改编码为中文编码，否则发送中文的时候，显示乱码
                    serialPort1.Encoding = Encoding.GetEncoding("GB2312");

                    //打开串口
                    serialPort1.Open();
                    writePortJson();
                }
                else
                {
                    //如果串口处于打开状态，则关闭串口
                    serialPort1.Close();

                    //重新刷新串口号列表
                    RefershPorts();
                }
            }
            catch (Exception ex)
            {
                //如果上边有异常，则跳入这里
                //MessageBox.Show()用于显示对话框
                MessageBox.Show(ex.Message);
            }
            finally
            {
                //根据串口状态控制控件状态
                if (serialPort1.IsOpen)
                {
                    CB_PortName.Enabled = false;    //两个下拉控件禁止点击更改
                    CB_Baud.Enabled = false;
                    BTN_Open.Text = "关闭";         //按键标签改成“关闭”  
                }
                else
                {
                    CB_PortName.Enabled = true;
                    CB_Baud.Enabled = true;
                    BTN_Open.Text = "打开";
                }
            }
        }

        #endregion

        #region 串口相关的事件函数

        //串口打开按钮的点击事件
        private void BTN_Open_Click(object sender, EventArgs e)
        {
            OpenOrClose();
        }
        #endregion

        #region Json相关的函数

        //全局变量settings，用于保存串口相关的数据
        PortSetting settings = new PortSetting();

        //写入配置文件的函数
        //如果想要移植，则需要根据自己的情况修改两个地方
        //1、函数内部第一行的参数settings改成自己的类变量
        //2、函数内部第二行参数"PortSettings.json"改成自己的配置文件路径和名字
        private void writePortJson()
        {
            //把保存数据的类变量settings通过Json语法生成字符串
            //1、settings是保存配置的类变量
            //2、JsonConvert.SerializeObject根据Json 语法把类变量里边的数据转化成字符串，赋值给jsonw变量
            string jsonw = Newtonsoft.Json.JsonConvert.SerializeObject(settings);

            //System.IO.File.WriteAllText函数表示覆盖写入字符串
            System.IO.File.WriteAllText("PortSettings.json", jsonw);
        }


        //读取配置函数
        //如果想要移植，则需要根据自己的情况修改几个地方
        //1、函数中出现的"PortSettings.json"都改成自己的配置文件路径和名字
        //2、函数JsonConvert.DeserializeObject<>尖括号"<>"中改成自己的配置类名字
        //3、配置类变量settings改成自己的变量名字
        private void ReadPortJson()
        {
            //判断程序根目录下有没有名字叫"PortSettings.json"的配置文件
            if (File.Exists("PortSettings.json"))
            {
                //如果有此文件
                //读取配置文件，把读取的块转化成JsonSetting类型
                //1、JsonConvert.DeserializeObject是Json读取文件函数
                //2、<PortSetting>表示读取完成的数据转化成PortSetting类型
                //3、System.IO.File.ReadAllText是根据文件路径读取文件里所有字符串的意思
                //4、最终生成的数据赋值给变量settings
                settings = JsonConvert.DeserializeObject<PortSetting>(System.IO.File.ReadAllText("PortSettings.json"));
            }
            else
            {
                //如果没有配置文件，则根据JsonSetting类的初始化，新建一个配置文件
                settings = new PortSetting();                           //初始化配置类，数据都是默认值
                string jsonw = JsonConvert.SerializeObject(settings);   //根据settings的数据，转换成配置文件的字符串数据
                System.IO.File.WriteAllText("PortSettings.json", jsonw);//覆盖写入配置文件
            }
        }

        //读取配置后初始化界面
        private void InitForm()
        {
            ReadPortJson();

            //如果配置文件里边保存的文件无效
            //则配置默认目录
            if (!Directory.Exists(settings.soverSettingPath))
            {
                settings.soverSettingPath = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
            }

            int index = CB_PortName.FindString(settings.portName);          //获取串口号对应的在CB_PortName控件中的索引
            CB_PortName.SelectedIndex = index;                              //CB_PortName选择索引，

            index = CB_Baud.FindString(settings.BaudRate.ToString());       //获取波特率对应的在CB_Baud控件中的索引
            CB_Baud.SelectedIndex = index;                                  //初始化波特率信息
        }

        //保存界面配置，并写入到配置文件中
        //可以在关闭程序前调用
        //也可以开个定时器定时保存
        private void saveForm()
        {
            settings.portName = CB_PortName.Text;                           //保存串口号
            settings.BaudRate = int.Parse(CB_Baud.Text);                    //波特率           
            writePortJson();
        }



        //定义舵机配置类
        SoverActivesSetting soverSetting = new SoverActivesSetting();
        private void writeSoverJson()
        {
            //文件波存路径，默认从settings中导入
            string fileName = settings.soverSetting;
            if (!File.Exists(fileName))
            {
                //如果settings中路径无效，则需要重新保存
                //SaveFileDialog是系统提供的保存窗口
                SaveFileDialog saveFileDialog = new SaveFileDialog();
                //.Filter属性用于过滤文件后缀名，这里默认使用.sover的后缀
                saveFileDialog.Filter = "舵机配置文件 (*.sover)|*.sover";

                //保存窗口的初始路径，默认放到软件根目录下
                saveFileDialog.InitialDirectory = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);

                //判断保存路径的过程是否完成
                if (saveFileDialog.ShowDialog() == DialogResult.OK)
                {
                    //如果正确保存文件，则变量fileName、配置文件settings.soverSetting的路径都要进行更改
                    fileName = settings.soverSetting = saveFileDialog.FileName;
                    //保存路径
                    settings.soverSettingPath = System.IO.Path.GetDirectoryName(fileName);
                }
                else
                {
                    //如果保存过程无效，则直接退出
                    MessageBox.Show("配置文件保存失败！");
                    return;
                }
            }
            //把配置类soverSetting的数据按照Json的格式转化成字符串
            string jsonw = JsonConvert.SerializeObject(soverSetting);
            //覆盖写入字符串到配置文件
            System.IO.File.WriteAllText(fileName, jsonw);
        }

        private void ReadSoverJson()
        {
            string fileName = settings.soverSetting;
            try
            {
                if (File.Exists(fileName))
                {
                    //如果文件路径有效，则把配置文件读取到类变量soverSetting中
                    soverSetting = JsonConvert.DeserializeObject<SoverActivesSetting>(System.IO.File.ReadAllText(fileName));
                    TMR_Save.Enabled = true;

                    //显示配置文件名
                    LAB_SettingName.Text = System.IO.Path.GetFileName(settings.soverSetting);
                }
                else
                {
                    //如果没有配置文件，则重新初始化一下soverSetting变量
                    soverSetting = new SoverActivesSetting();
                    LAB_SettingName.Text = "无配置文件";
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message + "\r\n可能是配置文件不兼容，删除配置重新生成！");
            }
        }

        //读取舵机配置文件并初始化界面
        //放在构造函数里边运行，按下“打开配置”按键、“新建配置”也可以运行
        private void InitSovers()
        {
            ReadSoverJson();
            NUD_SoverCount.Value = soverSetting.soverCount;
            //后续有其他功能则继续添加代码
        }

        //读取界面信息，保存舵机配置文件
        //可以程序关闭时调用，也可以10s定时调用保存，防止程序崩溃而未保存
        private void SaveSovers()
        {
            soverSetting.soverCount = (int)NUD_SoverCount.Value;
            //其他需要保存的代码放到此处

            writeSoverJson();
        }


        #endregion

        #region Json配置相关的事件
        private void TMR_Save_Tick(object sender, EventArgs e)
        {
            //根据界面保存配置
            saveForm();
            SaveSovers();
        }

        //程序关闭前的事件
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            //根据界面保存配置
            saveForm();
            SaveSovers();
        }
        private void BTN_OpenSover_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Filter = "舵机配置文件 (*.sover)|*.sover";
            ofd.InitialDirectory = settings.soverSettingPath;

            if (ofd.ShowDialog() == DialogResult.OK)
            {
                //如果正确打开文件，则配置文件settings.soverSetting的路径都要进行更改
                settings.soverSetting = ofd.FileName;
                settings.soverSettingPath = System.IO.Path.GetDirectoryName(settings.soverSetting);
                //重新初始化舵机界面
                InitSovers();                
            }
            else
            {
                //如果保存过程无效，则直接退出
                MessageBox.Show("配置文件打开失败！");
                return;
            }
        }
        private void BTN_NewSover_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            //.Filter属性用于过滤文件后缀名，这里默认使用.sover的后缀
            sfd.Filter = "舵机配置文件 (*.sover)|*.sover";
            sfd.InitialDirectory = settings.soverSettingPath;

            //判断保存路径的过程是否完成
            if (sfd.ShowDialog() == DialogResult.OK)
            {
                //如果正确保存文件，则变量fileName、配置文件settings.soverSetting的路径都要进行更改
                settings.soverSetting = sfd.FileName;               
                settings.soverSettingPath = System.IO.Path.GetDirectoryName(settings.soverSetting);

                LAB_SettingName.Text = System.IO.Path.GetFileName(settings.soverSetting);

            }
            else
            {
                //如果保存过程无效，则直接退出
                MessageBox.Show("配置文件保存失败！");
                return;
            }
        }

        #endregion

        #region 舵机操控界面
        //1、一个舵机有个小模块界面，包含内容如下：一个拖动条，可以拖动角度数据；一个数据框，可以显示和编辑角度；
        //  一个ID标签，可以显示舵机的ID；一个功能标签，显示当前舵机对应的功能；在配置模式下，显示最大角度和最小角度
        //2、每个舵机的小模块界面都是一致的，如果直接在Form1中配置，则费时费力不好管理，估推荐采用自定义控件的方法
        //   自定义控件的方法是右键工程“解决方案资源管理器”窗口，选择“添加”菜单“用户控件”，名字改为“SoverControl”
        //   自定义控件的的内容具体见工程“SoverControl.cs”
        //3、因为舵机数量不定，所以让代码很方便的进行布局，就不能按照之前学习拉控件的方法确定控件位置，
        //   这里推荐使用布局控件“flowLayoutPanel”，此控件不仅在编程方便布局，即便是程序运行之后，改变窗体大小，则里边的控件也能自动布局
        //   此控件的“Anchor”属性改为“上下左右”，保证布局控件能随着窗口大小而改变；“AutoScroll”属性改为True，当内部控件超出界限时，显示进度条
        //4、
        #endregion


    }
}
